package com.rslai.commons.util.excel;

import com.rslai.commons.util.StringUtil;
import com.rslai.commons.util.excel.exception.ExcelException;
import com.rslai.commons.util.exception.RunException;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 写入 Excel 解析类
 */
public class ExcelWrite {
    private HSSFWorkbook hssfWorkbook = null; // Excel 对象
    private Map<String, Sheet> sheetMap = new HashMap<String, Sheet>(); // sheet map（excel 中的所有 sheet）
    private Sheet sheet; // 当前激活的 sheet（工作表）

    /**
     * 构造函数（默认 sheetName - 工作表名称）
     */
    public ExcelWrite() {
        hssfWorkbook = new HSSFWorkbook(); // 创建一个webbook，对应一个Excel文件
        Sheet sheet = new Sheet(hssfWorkbook.createSheet()); // 在webbook中添加一个sheet,对应Excel文件中的sheet
        sheetMap.put(sheet.getSheetName(), sheet); // 将 sheet 用 sheetName 作为 key 保存到 sheetMap 中
        this.sheet = sheet; // 设置当前激活的
    }

    /**
     * 构造函数（指定 sheetName - 工作表名称）
     */
    public ExcelWrite(String sheetName) {
        if (StringUtil.isBlank(sheetName)) {
            throw new RunException("工作表名（sheetName） 不能为空");
        }
        sheetName = getLegalSheetName(sheetName); // 得到一个合法的 sheetName（工作表名称）
        hssfWorkbook = new HSSFWorkbook(); // 创建一个webbook，对应一个Excel文件
        Sheet sheet = new Sheet(hssfWorkbook.createSheet(sheetName)); // 在webbook中添加一个sheet,对应Excel文件中的sheet
        sheetMap.put(sheet.getSheetName(), sheet); // 将 sheet 用 sheetName 作为 key 保存到 sheetMap 中
        this.sheet = sheet; // 设置当前激活的
    }

    /**
     * 创建一个新工作表。创建后自动激活该 sheet（工作表）
     */
    public void createSheet() {
        Sheet sheet = new Sheet(hssfWorkbook.createSheet()); // 在webbook中添加一个sheet,对应Excel文件中的sheet
        sheetMap.put(sheet.getSheetName(), sheet); // 将 sheet 用 sheetName 作为 key 保存到 sheetMap 中
        this.sheet = sheet; // 设置当前激活的
    }

    /**
     * 创建一个新工作表。创建后自动激活该 sheet（工作表）
     * @param sheetName 工作表名称
     */
    public void createSheet(String sheetName) {
        if (StringUtil.isBlank(sheetName)) {
            throw new RunException("创建一个新工作表时 sheetName 不能为空");
        }
        sheetName = getLegalSheetName(sheetName); // 得到一个合法的 sheetName（工作表名称）
        Sheet temp = sheetMap.get(sheetName); // 查找是否已经有该 sheet（工作表）
        if (temp == null) {
            Sheet sheet = new Sheet(hssfWorkbook.createSheet(sheetName)); // 在webbook中添加一个sheet,对应Excel文件中的sheet
            sheetMap.put(sheet.getSheetName(), sheet); // 将 sheet 用 sheetName 作为 key 保存到 sheetMap 中
            this.sheet = sheet; // 设置当前激活的
        } else {
            this.sheet = temp; // 设置当前激活的
        }
    }

    /**
     * 激活一个工作表。没有则创建一个新工作表
     * @param sheetName 工作表名称
     */
    public void activateSheet(String sheetName) {
        if (StringUtil.isBlank(sheetName)) {
            throw new RunException("激活一个工作表时 sheetName 不能为空。如果想创建一个新工作表请调用 createSheet() 方法");
        }
        sheetName = getLegalSheetName(sheetName); // 得到一个合法的 sheetName（工作表名称）
        Sheet temp = sheetMap.get(sheetName);
        if (temp == null) {
            this.createSheet(sheetName); // 创建一个新工作表
        } else {
            this.sheet = temp; // 设置当前激活的
        }
    }

    /**
     * 返回头所在的行号，从0开始
     * @return 头所在的行号，从0开始
     */
    public int getHeadRow() {
        return sheet.getHeadRow();
    }

    /**
     * 设置头所在的行号
     * @param headRow 行号，从0开始，默认第0行是头
     * @throws ExcelException ExcelException
     */
    public void setHeadRow(int headRow) throws ExcelException {
        if (sheet.getHeadRow() == 0) {
            if (headRow != 0) {
                sheet.setHeadRow(headRow);
                sheet.setTotalRow(this.getTotalRow() + headRow);
            }
        } else {
            throw new ExcelException("head 所在行号已经设置完成，不能重复设置！");
        }
    }

    /**
     * 返回字段名对应的索引号（模糊匹配只要是字段名开头即可）,未找到抛出异常
     * @param name 字段名
     * @return 返回字段名对应的索引号
     * @throws ExcelException ExcelException
     */
    private int getHeadIndex(String name) throws ExcelException {
        for (Head value : sheet.getHeads().values()) {
            if ((null!=value.getName()) && value.getName().equals(name)) {
                return value.getIndex();
            }
        }
        throw new ExcelException("“" + name + "”字段未找到！");
    }

    /**
     * 返回索引号对应的字段名（未找到返回传入索引号）
     * @param index 索引号
     * @return 返回索引号对应的字段名
     * @throws ExcelException ExcelException
     */
    private String getHeadName(int index) {
        Head value = sheet.getHeads().get(index);
        if (null == value) {
            return index + "";
        } else {
            return value.getName();
        }
    }

    /**
     * 设置excel头
     * @param heads Head map
     */
	public void setHeads(HashMap<Integer, Head> heads) {
		sheet.setHeads(heads);
        sheet.setExistHead(true);
        sheet.setTotalCol(0);
        for (Integer key : heads.keySet()) {
            if (sheet.getTotalCol() <= heads.get(key).getIndex()) {
                sheet.setTotalCol(heads.get(key).getIndex() + 1);
            }
        }
        sheet.setTotalRow(sheet.getHeadRow() + 1);
    }

    /**
     * 返回总行数
     * @return 总行数
     */
    public int getTotalRow() {
        return sheet.getTotalRow();
    }

    /**
     * 增加一个Head
     * @param head head内容
     * @throws ExcelException ExcelException
     */
    public void addHead(Head head) throws ExcelException {
        if (null != sheet.getHeads().get(head.getIndex())) {
            throw new ExcelException("第“" + head.getIndex() + "”列已经设置为“" + sheet.getHeads().get(head.getIndex()).getName()  + "”，不能重复设置！");
        }
        sheet.getHeads().put(head.getIndex(), head);
        sheet.setTotalCol(head.getIndex() + 1);
        if (!sheet.isExistHead()) {
            sheet.setTotalRow(sheet.getHeadRow() + 1);
            sheet.setExistHead(true);
        }
    }

    /**
     * 增加一行数据
     * @param list ArrayList类型行数据
     */
    public void addBody(ArrayList<String> list) {
        sheet.getBodys().add(list);
        sheet.setTotalRow(sheet.getTotalRow() + 1);
    }

    /**
     * 增加一行数据
     * @param list String[]类型行数据
     */
    public void addBody(String[] list) {
        ArrayList<String> tmp = new ArrayList<String>();
        for (int i=0; i<list.length; i++) {
            tmp.add(list[i]);
        }
        sheet.getBodys().add(tmp);
        sheet.setTotalRow(sheet.getTotalRow() + 1);
    }

    /**
     * 在当前行增加注释，该注释加载当前行的最后，如果当前行字段数少于表格字段数会自动补齐
     * @param comment 注释
     */
    public void addCommentCurrentRow(String comment) {
        int currentRowNum;
        if (sheet.isExistHead()) {
            currentRowNum = sheet.getTotalRow() - sheet.getHeadRow() - 1;
        } else {
            currentRowNum = sheet.getTotalRow();
        }

        // 如果当前行字段少于表格字段数补""
        ArrayList row = sheet.getBodys().get(currentRowNum - 1);
        for (int i=row.size(); i<sheet.getTotalCol(); i++) {
            row.add("");
        }
        row.add(comment);
    }

    /**
     * 写入下拉框
     * @param head head数据
     */
	private void writeDropbox(Head head) {
        if ((null!=head.getOptions()) && (head.getOptions().length>0)) {
            // 超过255字符不生成下拉框
            int len = Arrays.toString(head.getOptions()).replace(" ", "").length() - 2;
            if (len > 255) {
                return;
            }

            // rage开始行号
            int startRow = 0;
            if (sheet.isExistHead()) {
                startRow = 1;
            }

            CellRangeAddressList regions = new CellRangeAddressList(startRow, 65535, head.getIndex(), head.getIndex()); //生成下拉列表列位
            DVConstraint constraint = DVConstraint.createExplicitListConstraint(head.getOptions()); // 生成下拉框内容
            HSSFDataValidation data_validation = new HSSFDataValidation(regions, constraint); // 绑定下拉框和作用区域
            sheet.getHssfSheet().addValidationData(data_validation); // 将 Range 添加到当前工作表
        }
	}

    /**
     * 写入头
     */
    private void writeHeads() {
        for (Sheet sheet : sheetMap.values()) {
            if (sheet.isExistHead()) {
                HSSFRow row = sheet.getHssfSheet().createRow(sheet.getHeadRow());
                HSSFCellStyle style = this.hssfWorkbook.createCellStyle();
                style.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 居中格式

                HSSFCell cell = null;
                for (Head value : sheet.getHeads().values()) {
                    cell = row.createCell(value.getIndex());
                    cell.setCellValue(value.getName());
                    cell.setCellStyle(style);
                    if (value.getWidth() != 0) {
                        sheet.getHssfSheet().setColumnWidth(value.getIndex(), value.getWidth());
                    }
                    writeDropbox(value);
                }
            }
        }
    }

    /**
     * 写入excel体数据
     */
    private void writeBodys() {
        for (Sheet sheet : sheetMap.values()) {
            // 数据开始行号
            int startRow = sheet.getHeadRow();
            if (sheet.isExistHead()) {
                startRow = sheet.getHeadRow() + 1;
            }

            // 自动换行 style
            HSSFCellStyle wrapStyle = this.hssfWorkbook.createCellStyle();
            wrapStyle.setWrapText(true);

            ArrayList rowData;
            for (int i = 0; i < sheet.getBodys().size(); i++) {
                HSSFRow row = sheet.getHssfSheet().createRow(i + startRow);
                rowData = sheet.getBodys().get(i);
                for (int j = 0; j < rowData.size(); j++) {
                    HSSFCell cell = row.createCell(j);

                    // isWrapText = true，设置自动换行
                    if (sheet.getHeads().get(j) != null && sheet.getHeads().get(j).isWrapText()) {
                        cell.setCellStyle(wrapStyle);
                    }

                    cell.setCellValue((String) rowData.get(j));
                }
            }
        }
    }

    /**
     * 写入excel文件
     * @param os 输出流
     * @return 输出流
     * @throws IOException IOException
     */
    public OutputStream write(OutputStream os) throws IOException {
        writeHeads(); // 写入excel头
        writeBodys(); // 写入体
        hssfWorkbook.write(os);
        os.flush();
        return os;
    }

    /**
     * 得到一个合法的 sheetName（工作表名称）
     * 1、将 :  \  /  ?  *  [  或  ] 转为 _
     * 2、截掉超过 31 字符长的内容
     * @param sheetName 工作表名称
     * @return
     */
    private String getLegalSheetName(String sheetName) {
        String temp = sheetName.replace(":", "_")
                .replace("\\", "_")
                .replace("/", "_")
                .replace("?", "_")
                .replace("*", "_")
                .replace("[", "_")
                .replace("]", "_");

        if (temp.length() > 31) {
            return temp.substring(0, 31);
        } else {
            return temp;
        }
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ExcelWrite{");

        for (Sheet tmp : sheetMap.values()) {
            sb.append(tmp.toString());
        }

        sb.append('}');
        return sb.toString();
    }

/*
    public static void main(String[] args) {
        OutputStream os = null;
        try {
            os = new FileOutputStream("./22.xls");

            ExcelWrite excelWrite = new ExcelWrite();
            excelWrite.setHeadRow(10);

            String[] s = new String[23];

            for (int i = 0; i < 23; i++) {
                s[i] = "123456789中";
            }
            HashMap<Integer, Head>map = new HashMap<Integer, Head>();
            Head head = new Head(1, "姓名", 15, s);
            Head head1 = new Head();
            //excelWrite.addHead(head);
            map.put(head.getIndex(), head);

            head1.setIndex(2);
            head1.setName("身份证");
            //excelWrite.addHead(head1);
            map.put(head1.getIndex(),head1);
            excelWrite.setHeads(map);

            ArrayList<String> list = new ArrayList<String>();
            list.add("1");

            excelWrite.addBody(list);
            excelWrite.addCommentCurrentRow("list");

            excelWrite.addBody(new String[]{"dddd","33333","44444"});
            excelWrite.addCommentCurrentRow("11111----");

            excelWrite.write(os);

            System.out.println("写入成功！");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != os) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
*/
}
